// Copyright 2011, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_FILE_WINDOWS_BASIC_FILE_IMPL_HPP_
#define LIBV_FILE_WINDOWS_BASIC_FILE_IMPL_HPP_

#ifndef _WIN32
# error "This header should be used in Windows only."
#endif

#include <limits>
#include <string>
#include <libv/windows_lean.h>
#include <libv/file/basic_file.h>



namespace libv
{

FileHandle BasicFile::BasicOpenImpl(const ::std::string& file_path,
                                    const int open_type,
                                    const bool enable_read_ahead_caching,
                                    const int additional_flags,
                                    const bool read_only) const
{
    const DWORD rw_mode = GENERIC_READ | ( read_only ? 0 : GENERIC_WRITE );

    // BasicOpen должен проверить, что open_type — одно из трех значений.
    const int open_mode =
        ( open_type == OpenType::kCreateIfNotExist ) ? CREATE_NEW :
        ( open_type == OpenType::kOpenExisting ) ? OPEN_EXISTING :
        OPEN_ALWAYS;

    int flags = additional_flags;
    if ( !enable_read_ahead_caching )
        flags |= FILE_FLAG_RANDOM_ACCESS;
    else
        flags |= FILE_FLAG_SEQUENTIAL_SCAN;


    const HANDLE h = ::CreateFileA(
        file_path.c_str(),
        rw_mode,
        FILE_SHARE_WRITE |   // повторяем семантику POSIX: файл, созданный
          FILE_SHARE_READ |  // одним процессом может быть прочитан, дописан или
          FILE_SHARE_DELETE, // удален другим.
        NULL,                // игнорируем атрибуты безопасности
        static_cast<DWORD>(open_mode),  // такие мелочи как знаковость типов
        static_cast<DWORD>(flags),      // в дефайнах майкрософт игнорирует
        NULL);
    last_error_ = ::GetLastError();

    return h;
}



bool BasicFile::CloseImpl() const
{
    const bool result = ( ::CloseHandle(file_handle_) != FALSE );
    last_error_ = ::GetLastError();
    return result;
}



fpos64 BasicFile::SizeImpl() const
{
    LARGE_INTEGER file_size = { 0 };
    const bool result = ( ::GetFileSizeEx(file_handle_, &file_size) != FALSE );
    last_error_ = ::GetLastError();

    return result ? file_size.QuadPart : -1;
}



bool BasicFile::ResizeImpl(const fpos64 size)
{
    LARGE_INTEGER li_size;
    li_size.QuadPart = size;
    const bool result =
        ::SetFilePointerEx(file_handle_, li_size, NULL, FILE_BEGIN) != FALSE &&
        ::SetEndOfFile(file_handle_) != FALSE;
    last_error_ = ::GetLastError();

    return result;
}



bool BasicFile::FlushImpl()
{
    const bool result = ( ::FlushFileBuffers(file_handle_) != FALSE );
    last_error_ = ::GetLastError();

    return result;
}



bool BasicFile::BasicLockImpl(const int lock_type, const bool block) const
{
    OVERLAPPED o = { 0 };  // начальное смещение в файле — 0.

    // Создаем событие, которое мигнет, когда операция завершится.
    // Для синхронного файла это событие ничего не сделает.
    // Но создавать 2 реализации плохо — приходилось выносить их в разные классы
    // и тянуть вместе с ними все методы, их использующие (например, Close()).
    o.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
    last_error_ = ::GetLastError();
    if ( o.hEvent == NULL )
        return false;


    DWORD flags = 0;
    if ( lock_type == LockType::kExclusiveLock )
        flags |= LOCKFILE_EXCLUSIVE_LOCK;

    if ( !block )
        flags |= LOCKFILE_FAIL_IMMEDIATELY;

    bool result = ( ::LockFileEx(
        file_handle_,
        flags,
        0,                                    // зарезервировано
        ::std::numeric_limits<DWORD>::max(),  // num of bytes low
        ::std::numeric_limits<DWORD>::max(),  // num of bytes high
        &o) != FALSE);
    last_error_ = ::GetLastError();


    if ( !result )
    {
        // Не получили блокировку, выясняем причину.
        if ( !block )
        {
            // Не ошибка: не удался try.
            // Нельзя объединять эти 2 if'а — нарушится семантика!
            if ( last_error_ == ERROR_LOCK_VIOLATION )
                last_error_ = LIBV_FILE_NO_ERROR;
        }

        // Ушел асинхронный запрос.
        else if ( last_error_ == ERROR_IO_PENDING )
        {
            result = ::WaitForSingleObject(o.hEvent, INFINITE) == WAIT_OBJECT_0;
            last_error_ = ::GetLastError();
        }
    }

    ::CloseHandle(o.hEvent);
    return result;
}


bool BasicFile::UnlockImpl() const
{
    // UnlockFile в отличие от UnlockFileEx всегда синхронный.
    const bool result = ( ::UnlockFile(
        file_handle_,
        0,
        0,
        ::std::numeric_limits<DWORD>::max(),
        ::std::numeric_limits<DWORD>::max()) != FALSE );
    last_error_ = ::GetLastError();

    return result;
}

}  // libv

#endif  // LIBV_FILE_WINDOWS_BASIC_FILE_IMPL_HPP_
